#include <cpptrace/cpptrace.hpp>

#include <algorithm>
#include <cctype>
#include <iostream>
#include <string>
#include <pmparser.h>
#include <assert.h>
#include <unordered_map>
#include <vector>

void trace() {
    const auto raw_trace = cpptrace::generate_raw_trace();
    procmaps_iterator* maps_it = pmparser_parse(-1);
    assert(maps_it != NULL);
    procmaps_struct* maps = NULL;
    std::vector<std::tuple<std::string, std::uintptr_t, std::uintptr_t>> module_ranges;
    while( (maps = pmparser_next(maps_it)) != NULL){
        auto cur_start_addr = reinterpret_cast<uintptr_t>(maps->addr_start);
        auto cur_end_addr = reinterpret_cast<uintptr_t>(maps->addr_end);
        bool exists = false;
        for (auto idx = 0u; idx < module_ranges.size(); idx++) {
            auto [pathname, start_addr, end_addr] = module_ranges[idx];
            if (pathname == maps->pathname) { 
                cur_start_addr = std::min(cur_start_addr, start_addr); 
                cur_end_addr = std::max(cur_end_addr, end_addr); 
                module_ranges[idx] = std::make_tuple(maps->pathname, cur_start_addr, cur_end_addr);
                exists = true;
                break;
            }
        }
        if (!exists)
            module_ranges.emplace_back(std::make_tuple(maps->pathname, cur_start_addr, cur_end_addr));
    }
    pmparser_free(maps_it);
    std::vector<std::tuple<std::string, std::uintptr_t>> frames;
    const static std::uintptr_t executable_start_addr = 0x400000;
    for (auto frame_addr : raw_trace.frames) {
        for (auto [pathname, start_addr, end_addr] : module_ranges) {
            if (frame_addr >= start_addr && frame_addr <= end_addr) {
                if (start_addr == executable_start_addr) {
                    frames.emplace_back(std::make_tuple(pathname, frame_addr));
                } else {
                    auto rel_frame_addr = frame_addr - start_addr;
                    frames.emplace_back(std::make_tuple(pathname, rel_frame_addr));
                }
            }
        }
    }
    for (auto [pathname, frame_addr] : frames) {
        printf("%s\t0x%lx\n", pathname.c_str(), frame_addr);
    }
}

void foo(int n) {
    if(n == 0) {
        trace();
    } else {
        foo(n - 1);
    }
}

template<typename... Args>
void foo(int, Args... args) {
    foo(args...);
}

void function_two(int, float) {
    foo(1, 2);
}

void function_one(int) {
    function_two(0, 0);
}
